//-------------------------------------------------------------------
//	Copyright (c) 2012-2014 Zhirnov Andrey
//	This file is part of the "UXGEN-ES" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------

#pragma once

#include "Base/Resource/Resource.h"
#include "Platforms/Engine.Platforms.h"

namespace Engine
{
namespace Base
{

	namespace EResManagment
	{
		enum type
		{
			NONE			= 0,
			CACHE_RESOURCES	= 1 << 0,

			DEFAULT			= CACHE_RESOURCES,
		};
	}



	//
	// Resource Manager
	//

	class ResourceManager : public BaseObject
	{
	// types
	public:
		typedef bool (* LoadResourceFromFile_t) (ResourcePtr &resource, const RFilePtr &file, void *parameter,
												 const Resource::ResLoadParams &loadParams, const EngineSubSystemsRef ess);
		
		typedef bool (* LoadResourceWithName_t) (ResourcePtr &resource, uni_c_string filename, void *parameter,
												 const Resource::ResLoadParams &loadParams, const EngineSubSystemsRef ess);

	private:
		typedef map< string, ResourcePtr >	res_map_t;

		//
		// Resource Map
		//
		struct ResourceMap
		{
		// variables
		public:
			LoadResourceFromFile_t	fLoader;
			LoadResourceWithName_t	nLoader;
			void *					parameter;
			res_map_t				resources;
			EResManagment::type		flags;

		// methods
		public:
			ResourceMap (LoadResourceFromFile_t fLoader, LoadResourceWithName_t nLoader, void *parameter, EResManagment::type flags) :
				fLoader(fLoader), nLoader(nLoader), parameter(parameter), flags(flags)
			{}

			ResourceMap (uni_c_string name, const ResourcePtr &res) :
				fLoader(null), nLoader(null), parameter(null), flags(EResManagment::DEFAULT)
			{
				resources.Add( name, res );
			}

			bool Load (const RFilePtr &file, ResourcePtr &res, uni_c_string name,
					   const Resource::ResLoadParams &loadParams, const EngineSubSystemsRef ess)
			{
				CHECK_ERR( fLoader != null );
				CHECK_ERR( fLoader( res, file, parameter, loadParams, ess ) );

				if ( EnumCmp( flags, EResManagment::CACHE_RESOURCES ) )
				{
					resources.AddOrReplace( name, res );
				}
				return true;
			}

			bool Load (ResourcePtr &res, uni_c_string name,
					   const Resource::ResLoadParams &loadParams, const EngineSubSystemsRef ess)
			{
				CHECK_ERR( nLoader != null );
				CHECK_ERR( nLoader( res, name, parameter, loadParams, ess ) );

				if ( EnumCmp( flags, EResManagment::CACHE_RESOURCES ) )
				{
					resources.AddOrReplace( name, res );
				}
				return true;
			}

			bool CanLoadForName () const
			{
				return nLoader != null;
			}
		};

		typedef map< EResource::type, ResourceMap >	resources_map_t;


	// variables
	private:
		resources_map_t		_resourceMap;


	// methods
	public:
		ResourceManager (const EngineSubSystemsRef ess);
		~ResourceManager ();

		void Clear ();

		void RegisterLoader (EResource::type resourceType, LoadResourceFromFile_t floader, LoadResourceWithName_t nloader,
							 void *parameter, EResManagment::type resMngmt = EResManagment::DEFAULT);

		void UnregisterLoader (EResource::type resourceType, LoadResourceFromFile_t floader,
								LoadResourceWithName_t nloader, void *parameter);

		void UnregisterAllLoaders ();

		bool AddResource (uni_c_string name, const ResourcePtr &res);
		void RemoveResource (const ResourcePtr &res);

		void RemoveResources (EResource::type resType);
		void RemoveAllResources ();


		template <typename T>
		bool Load (EResource::type resType, uni_c_string filename, T &res);

		template <typename T, typename R>
		bool Load (EResource::type resType, uni_c_string filename, T &res, const R &loadParams);
		
		
		template <typename T>
		bool Load (EResource::type resType, const RFilePtr &file, T &res);

		template <typename T, typename R>
		bool Load (EResource::type resType, const RFilePtr &file, T &res, const R &loadParams);
		

		bool LoadFromAddress (EResource::type resType, uni_c_string filename,
								ResourcePtr &res, const Resource::ResLoadParams &loadParams);

		bool LoadFromFile (EResource::type resType, const RFilePtr &file, ResourcePtr &res,
							const Resource::ResLoadParams &loadParams, uni_c_string name = "");


		bool GetResource (EResource::type resType, uni_c_string name,
						  ResourcePtr &res, const Resource::ResLoadParams &loadParams);

		usize GetUsedMemorySize () const;
	};


	
/*
=================================================
	constructor
=================================================
*/
	inline ResourceManager::ResourceManager (const EngineSubSystemsRef ess) :
		BaseObject(ess)
	{
		ESS()->SetResourceManager( this );
	}
		
/*
=================================================
	destructor
=================================================
*/
	inline ResourceManager::~ResourceManager ()
	{
		ESS()->SetResourceManager( null );
	}
	
/*
=================================================
	Clear
=================================================
*/
	inline void ResourceManager::Clear ()
	{
		_resourceMap.Clear();
	}
	
/*
=================================================
	RegisterLoader
=================================================
*/
	inline void ResourceManager::RegisterLoader (EResource::type resourceType, LoadResourceFromFile_t floader,
												 LoadResourceWithName_t nloader, void *parameter, EResManagment::type resMngmt)
	{
		usize	index = -1;

		if ( _resourceMap.FindIndex( resourceType, index ) )
		{
			_resourceMap[index].second.nLoader	 = nloader;
			_resourceMap[index].second.fLoader	 = floader;
			_resourceMap[index].second.parameter = parameter;
			_resourceMap[index].second.flags	 = resMngmt;
		}
		else
		{
			_resourceMap.Add( resourceType, ResourceMap( floader, nloader, parameter, resMngmt ) );
		}
	}
	
/*
=================================================
	UnregisterLoader
=================================================
*/
	inline void ResourceManager::UnregisterLoader (EResource::type resourceType, LoadResourceFromFile_t floader,
													LoadResourceWithName_t nloader, void *parameter)
	{
		usize	index = -1;

		if ( _resourceMap.FindIndex( resourceType, index ) )
		{
			ResourceMap & rm = _resourceMap[index].second;

			if ( rm.fLoader == floader and rm.nLoader == nloader and rm.parameter == parameter )
			{
				rm.fLoader	 = null;
				rm.nLoader	 = null;
				rm.parameter = null;
			}
		}
	}
	
/*
=================================================
	UnregisterAllLoaders
=================================================
*/
	inline void ResourceManager::UnregisterAllLoaders ()
	{
		FOR( i, _resourceMap )
		{
			if ( _resourceMap[i].second.resources.Empty() )
			{
				_resourceMap.EraseFromIndex( i );
				--i;
				continue;
			}

			_resourceMap[i].second.fLoader	 = null;
			_resourceMap[i].second.nLoader	 = null;
			_resourceMap[i].second.parameter = null;
		}
	}
	
/*
=================================================
	AddResource
=================================================
*/
	inline bool ResourceManager::AddResource (uni_c_string name, const ResourcePtr &res)
	{
		CHECK_ERR( not name.Empty() and res.IsNotNull() );

		resources_map_t::iterator	res_type;
		
		if ( _resourceMap.Find( res->GetType(), res_type ) )
		{
			res_type->second.resources.Add( name, res );
		}
		else
		{
			_resourceMap.Add( res->GetType(), ResourceMap( name, res ) );
		}
	}
	
/*
=================================================
	RemoveResource
=================================================
*/
	inline void ResourceManager::RemoveResource (const ResourcePtr &res)
	{
		CHECK_ERR( res.IsNotNull(), void() );
		
		resources_map_t::iterator	res_type;
		res_map_t::iterator			resource;

		if ( _resourceMap.Find( res->GetType(), res_type ) )
		{
			FOR( i, res_type->second.resources )
			{
				if ( res_type->second.resources[i].second == res )
				{
					res_type->second.resources.EraseFromIndex( i );
					return;
				}
			}
		}
	}
	
/*
=================================================
	RemoveResources
=================================================
*/
	inline void ResourceManager::RemoveResources (EResource::type resType)
	{
		resources_map_t::iterator	res_type;

		if ( _resourceMap.Find( resType, res_type ) )
		{
			res_type->second.resources.Clear();
		}
	}
	
/*
=================================================
	RemoveAllResources
=================================================
*/
	inline void ResourceManager::RemoveAllResources ()
	{
		FOR( i, _resourceMap )
		{
			_resourceMap[i].second.resources.Clear();
		}
	}
	
/*
=================================================
	Load
=================================================
*/
	template <typename T>
	inline bool ResourceManager::Load (EResource::type resType, uni_c_string filename, T &res)
	{
		return Load( resType, filename, res, T::value_t::GetDefaultLoadParams() );
	}
	
/*
=================================================
	Load
=================================================
*/
	template <typename T, typename R>
	inline bool ResourceManager::Load (EResource::type resType, uni_c_string filename, T &res, const R &loadParams)
	{
		return LoadFromAddress( resType, filename, (ResourcePtr &) res, (const Resource::ResLoadParams &) loadParams );
	}
	
/*
=================================================
	Load
=================================================
*/
	template <typename T>
	inline bool ResourceManager::Load (EResource::type resType, const RFilePtr &file, T &res)
	{
		return Load( resType, file, res, T::value_t::GetDefaultLoadParams() );
	}
	
/*
=================================================
	Load
=================================================
*/
	template <typename T, typename R>
	inline bool ResourceManager::Load (EResource::type resType, const RFilePtr &file, T &res, const R &loadParams)
	{
		return LoadFromFile( resType, file, (ResourcePtr &) res, (const Resource::ResLoadParams &) loadParams );
	}

/*
=================================================
	LoadFromAddress
=================================================
*/
	inline bool ResourceManager::LoadFromAddress (EResource::type resType, uni_c_string filename,
													ResourcePtr &res, const Resource::ResLoadParams &loadParams)
	{
		if ( GetResource( resType, filename, res, loadParams ) )
			return true;

		resources_map_t::iterator	res_type;
		
		CHECK_ERR( _resourceMap.Find( resType, res_type ) );

		if ( res_type->second.CanLoadForName() )
		{
			CHECK( res_type->second.Load( res, filename, loadParams, ESS() ) );
		}

		if ( res.IsNull() )
		{
			const bool	file_exist = ESS()->GetFileSystem()->IsFileExist( filename );
			
			if ( file_exist ) {
				RFilePtr	file;
				CHECK_ERR( ESS()->GetFileSystem()->OpenForRead( filename, file ) );
				CHECK_ERR( res_type->second.Load( file, res, filename, loadParams, ESS() ) );
			}
			else {
				RFilePtr	file;
				CHECK_ERR( ESS()->GetFileSystem()->OpenResource( filename, file ) );
				CHECK_ERR( res_type->second.Load( file, res, filename, loadParams, ESS() ) );
			}
		}
		
		CHECK_ERR( res.IsNotNull() );
		return true;
	}
	
/*
=================================================
	LoadFromFile
=================================================
*/
	inline bool ResourceManager::LoadFromFile (EResource::type resType, const RFilePtr &file, ResourcePtr &res,
												const Resource::ResLoadParams &loadParams, uni_c_string name)
	{
		resources_map_t::iterator	res_type;
		
		CHECK_ERR( _resourceMap.Find( resType, res_type ) );
		
		uni_c_string res_name = ( not name.Empty() ? (uni_c_string)name :
								( not file->Name().Empty() ?
								(uni_c_string)file->Name() : (uni_c_string)"default" ) );

		CHECK_ERR( res_type->second.Load( file, res, res_name, loadParams, ESS() ) );
		CHECK_ERR( res.IsNotNull() );
		return true;
	}
	
/*
=================================================
	GetResource
=================================================
*/
	inline bool ResourceManager::GetResource (EResource::type resType, uni_c_string name,
											  ResourcePtr &res, const Resource::ResLoadParams &loadParams)
	{
		CHECK_ERR( not name.Empty() );

		resources_map_t::iterator	res_type;

		res = null;

		if ( _resourceMap.Find( resType, res_type ) )
		{
			usize	first = -1;
			usize	last  = -1;

			if ( res_type->second.resources.FindFirstIndex( name, first ) and
				 res_type->second.resources.FindLastIndex( first, last ) )
			{
				res_map_t const &	resources = res_type->second.resources;

				for (; first < last; ++first)
				{
					if ( resources[first].second->IsLoadedWithParams( loadParams ) )
					{
						res = resources[first].second;
						return true;
					}
				}
			}
		}
		return false;
	}
	
/*
=================================================
	GetUsedMemorySize
=================================================
*/
	inline usize ResourceManager::GetUsedMemorySize () const
	{
		usize	size = 0;

		FOR( i, _resourceMap )
		{
			res_map_t const &	resources = _resourceMap[i].second.resources;

			FOR( j, resources )
			{
				size += resources[j].second->GetMemSize();
			}
		}
		return size;
	}


}	// Base
}	// Engine